home *** CD-ROM | disk | FTP | other *** search
/ TeX 1995 July / TeX CD-ROM July 1995 (Disc 1)(Walnut Creek)(1995).ISO / web / noweb / src / icon / l2h.nw (.txt) < prev    next >
LaTeX Document  |  1995-02-24  |  56KB  |  1,582 lines

  1. \documentstyle[noweb,multicol]{article}
  2. \title{Converting {\LaTeX} to HTML}
  3. \author{Norman Ramsey\\{\tt norman@bellcore.com}}
  4. \noweboptions{smallcode}
  5. \setcounter{secnumdepth}{1}
  6. \begin{document}
  7. \pagenumbering{roman}
  8. \maketitle
  9. \tableofcontents
  10. \pagenumbering{arabic}
  11. \section{Introduction}
  12. This program provides an infrastructure for converting {\LaTeX} to
  13. HTML.
  14. That infrastructure can be used to make a {\tt noweb} filter or to
  15. make a standalone conversion program.
  16. The program is roughly divided into three parts.
  17. Section~\ref{cs-decls} assigns a meaning (treatment) to each control
  18. sequence.
  19. It is roughly declarative, and the hope is that one day it can
  20. be replaced by a data file, which could be augmented dynamically.
  21. (The central flaw in this program is that all {\TeX} control
  22. sequences must be hard-wired.)
  23. Section~\ref{engine} describes the engine used to do the conversion,
  24. Sections \ref{imp-decl}~and~\ref{html-format} gives the procedures that do the individual conversions.
  25. \section{Descriptions of control sequences}
  26. \label{cs-decls}
  27. This section defines behavior for each control sequence we know how to
  28. convert.
  29. The definitions have a declarative flavor, since most are done by
  30. procedure calls.
  31. These calls initialize the machinery descriped in Section~\ref{cs-tables}.
  32. {\LaTeX} control sequences come first, using
  33.  the same organization as the quick reference card from the 
  34. second edition of the {\LaTeX} manual.
  35. Other control sequences follow.
  36. \subsection{{\LaTeX} control sequences}
  37. \subsubsection{Sentences and paragraphs}
  38. <<control-sequence assignments>>=
  39. substitution(",", " ")
  40. substitution(" ", " ")
  41. substitution("\n", "\n")
  42. substitution("\t", " ")
  43. ignore("@")
  44. ignore("/")            # no italic correction
  45. substitution("", "\n")        # \<newline> treated as request for newline
  46. every c := !"$%#{}_" do
  47.   substitution(c, c)
  48. substitution("&", "&")
  49. argblock("emph", "em")
  50. argblock("footnote",     "<b>[</b>", "<b>]</b>")   # put footnotes in bold brackets
  51. argblock("footnotetext", "<b>[</b>", "<b>]</b>")
  52. argblock("thanks",       "<b>[</b>", "<b>]</b>")   # put footnotes in bold brackets
  53. substitution("today", &date)
  54. \subsubsection{Type style}
  55. <<control-sequence assignments>>=
  56. ignore("textrm")        # html can't switch to default font!
  57. argblock("textit", "i")     
  58. argblock("textbf", "b")     
  59. argblock("textsl", "i")     
  60. ignore("textsc")
  61. argblock("texttt", "tt")
  62. ignore("textsf")
  63. ignore("boldmath")
  64. # \boldmath could be done by introducing S.mathfont, but I don't want to!
  65. <<control-sequence assignments>>=
  66. ignore("mathrm")        # html can't switch to default font!
  67. argblock("mathit", "i")     
  68. argblock("mathbf", "b")     
  69. argblock("mathtt", "tt")
  70. ignore("mathsf")
  71. argblock("mathcal", "i")
  72. @ HTML has only one size.
  73. <<control-sequence assignments>>=
  74. every ignore("tiny" | "scriptsize" | "footnotesize" | "small" | "normalsize" | 
  75.              "large" | "Large" | "LARGE" | "huge" | "Huge")
  76. \subsubsection{Accents and symbols}
  77. I couldn't find an official way to do symbols.
  78. Maybe when CERN comes back up I can find the ISO Latin~1 character set.
  79. <<control-sequence assignments>>=
  80. every accent(key(accent_name))
  81. every ignore("dag" | "ddag" | "S" | "P" | "copyright" | "pounds")
  82. \subsubsection{Sectioning and table of contents}
  83. <<control-sequence assignments>>=
  84. argblockv("part",          "h1", &null, "*[")
  85. argblockv("chapter",       "h1", &null, "*[")
  86. argblockv("section",       "h2", &null, "*[")     
  87. argblockv("subsection",    "h3", &null, "*[")     
  88. argblockv("subsubsection", "h4", &null, "*[")     
  89. argblockv("paragraph",     "h5", &null, "*[")     
  90. argblockv("subparagraph",  "h6", &null, "*[")     
  91. ignore("appendix")
  92. auxfile("tableofcontents", "toc", "<p><b>[Table of contents]</b><p>", 
  93.         "<h2>Table of Contents</h2>")
  94. cstab["tableofcontents"] := Ctableofcontents # override to call set_toclevel
  95. ignore("listoftables")
  96. \subsubsection{Mathematical formulas}
  97. Here we see our first assignments to [[cstab]], which is the real
  98. technology underlying these seemingly declarative calls.
  99. I'll assign to [[cstab]] directly when some really special behavior is
  100. called for.  In this case, it's going in and out of math mode.
  101. <<control-sequence assignments>>=
  102. cstab["("] := Cmath
  103. cstab[")"] := Cmath_end
  104. cstab["["] := Cdisplaymath
  105. cstab["]"] := Cdisplaymath_end
  106. ignoreenv("equation")
  107. every table_env(star("eqnarray"), 0, " ", "blockquote")  # also lame
  108. substitution("frac", "<b>frac</b>")
  109. substitution("sqrt", "<b>frac</b>")
  110. every substitution("ldots" | "cdots" | "vdots", "...")
  111. ignore("left")
  112. ignore("right")
  113. ignore("overline")
  114. substitution(":", " ")
  115. substitution(";", " ")
  116. ignore("!")
  117. @ The [[star]] procedure lets us define \verb+eqnarray+ and
  118. \verb+eqnarray*+ in one fell swoop.
  119. <<*>>=
  120. procedure star(cs)
  121.   suspend cs | (cs || "*")
  122. There are a gazillion symbols.  I'll add them on demand.
  123. <<control-sequence assignments>>=
  124. substitution("Diamond", "<>")
  125. substitution("langle",  "<")
  126. substitution("rangle",  ">")
  127. substitution("le",      "<=")
  128. substitution("ge",      ">=")
  129. substitution("bmod", "</i>mod<i>") # better hook in with math
  130. substitution("equiv", "===")
  131. \subsubsection{Displayed paragraphs}
  132. HTML really has only one kind of displayed paragraph---the block quotation.
  133. <<control-sequence assignments>>=
  134. envblock("quote", "blockquote")
  135. envblock("quotation", "blockquote")
  136. envblock("center", "blockquote")
  137. envblock("flushleft", "blockquote")
  138. envblock("flushright", "blockquote")
  139. envblock("verse", "blockquote")
  140. begintab["verbatim"] := Cverbatim
  141. begincl["verbatim"] := verbatim_cl("pre", "\\end{verbatim}")
  142. cstab["verb"] := Cverb
  143. \subsubsection{Lists}
  144. <<control-sequence assignments>>=
  145. cstab["item"] := Citem
  146. csclosure["item"] := [item_cl("<li>", "", "<li>")]
  147. listenv("itemize", "ul")
  148. listenv("enumerate", "ol")
  149. listenv("description", "dl")
  150. \subsubsection{???}
  151. <<control-sequence assignments>>=
  152. ignore("documentstyle", "[{")
  153. ignore("documentclass", "[{")
  154. ignore("usepackage", "[{")
  155. ignore("pagestyle", "{")
  156. ignore("pagenumbering", "{")
  157. \subsubsection{Title page and abstract}
  158. I could be clever and have \verb+\title+ have a side effect
  159. that sticks in the right boilerplate when we see \verb+\begin{document}+,
  160. but for now it's not worth the hassle.
  161. <<control-sequence assignments>>=
  162. argblockv("title", "h1")
  163. argblockv("author","address")
  164. argblockv("date",  "b")
  165. substitution("maketitle", "<!--title goes here-->")
  166. ignoreenv("titlepage")
  167. envblock("abstract", "<h2>Abstract</h2><blockquote>", "</blockquote>")
  168. \subsubsection{Cross-reference}
  169. A more ambitious scheme would make labels anchor at preceding
  170. sectioning commands, but it's hard to see how to do that in one pass.
  171. Instead, I just use some conventional glyphs.
  172. I use special procedures for the cross-references so I can have an
  173. arrow pointing either forward or backward, depending on the direction
  174. of the reference.
  175. <<control-sequence assignments>>=
  176. cstab["label"] := Clabel
  177. cstab["ref"] := Cref
  178. cstab["pageref"] := Cref
  179. \subsubsection{Bibliography and citation}
  180. For the bibliography, I actually go grubbing for a {\tt .bbl} file if
  181. I can find one.
  182. <<control-sequence assignments>>=
  183. ignore("bibliographystyle", "{")
  184. auxfile("bibliography", "bbl", "<b>[BibTeX bibliography]</b>", &null, "{")
  185. envblock("thebibliography", "<h2>References</h2>", "", "{")
  186. cstab["cite"] := Ccite
  187. cstab["bibitem"] := Cbibitem
  188. ignore("newblock")
  189. ignore("nocite", "{")
  190. \subsubsection{Splitting the input}
  191. All input is ignored.  Those things are in their own files.
  192. <<control-sequence assignments>>=
  193. every ignore("input" | "include" | "includeonly", "{")
  194. # filecontents not done yet
  195. \subsubsection{Line breaking}
  196. <<control-sequence assignments>>=
  197. substitution("\\", "<br>", "[")
  198. substitution("linebreak", "<br>")
  199. ignore("-")
  200. ignoreenv("sloppypar")
  201. ignore("sloppy")
  202. \subsubsection{Page breaking}
  203. I simulate forced page breaks by horizontal rules.
  204. <<control-sequence assignments>>=
  205. substitution("pagebreak", "<hr>")
  206. substitution("newpage", "<hr>")
  207. substitution("clearpage", "<hr>")
  208. ignore("enlargethispage", "*{")
  209. \subsubsection{Boxes}
  210. <<control-sequence assignments>>=
  211. ignore("mbox")
  212. ignore("makebox", "([[")  # ( comes from picture area
  213. ignore("fbox")
  214. ignore("framebox", "[[")  # could insert horizontal rules, but why?
  215. ignore("newsavebox", 1)
  216. ignore("sbox", 2)
  217. ignore("savebox", "{[[{")
  218. ignore("usebox", 1)
  219. envblock("minipage", "blockquote", &null, "[{")
  220. argblock("parbox",   "blockquote", &null, "[{")
  221. \subsubsection{Space}
  222. <<control-sequence assignments>>=
  223. ignore("hspace", "*{")
  224. ignore("hfil")
  225. ignore("hfill")
  226. ignore("vspace", "*{")
  227. ignore("vfil")
  228. ignore("vfill")
  229. \subsubsection{Length}
  230. <<control-sequence assignments>>=
  231. ignore("newlength", "{")
  232. ignore("setlength", "{{")
  233. ignore("addtolength", "{{")
  234. \subsubsection{Pictures}
  235. <<control-sequence assignments>>=
  236. envblock("picture", "<b>[picture]</b>", "", "((")
  237. ignore("put", "({")
  238. ignore("multiput", "(({{")
  239. ignore("dashbox", "{([")
  240. ignore("line", "({")
  241. ignore("vector", "({")
  242. ignore("shortstack", "[")
  243. ignore("circle", "*{")
  244. ignore("oval", "([")
  245. ignore("frame")
  246. ignore("thinlines")
  247. ignore("thicklines")
  248. \subsubsection{Figures and Tables}
  249. I surround figures and tables with horizontal rules.
  250. <<control-sequence assignments>>=
  251. every envblock(star("figure"), "<hr>", "<hr>", "[")
  252. every envblock(star("table"),  "<hr>", "<hr>", "[")
  253. argblock("caption", "b")  # captions in bold
  254. \subsubsection{{\tt tabbing} environment}
  255. I can't see how to do anything sensible with {\tt tabbing}.
  256. <<control-sequence assignments>>=
  257. envblock("tabbing", "blockquote")
  258. # \= is accent
  259. ignore(">")
  260. ignore("+")
  261. ignore("kill")
  262. \subsubsection{{\tt array} and {\tt tabular} environment}
  263. <<control-sequence assignments>>=
  264. envblock("array",    "blockquote", &null, "[{")
  265. envblock("tabular",  "blockquote", &null, "[{")
  266. envblock("tabularx", "blockquote", &null, "[{")
  267. ignore("multicolumn", "{{")
  268. substitution("hline", "<hr>")
  269. ignore("cline", "{")
  270. \subsubsection{Definitions}
  271. <<control-sequence assignments>>=
  272. ignore("newcommand", "A[[{")
  273. ignore("renewcommand", "A[[{")
  274. ignore("newenvironment", "{[{{")
  275. ignore("renewenvironment", "{[{{")
  276. ignore("newtheorem", "{{")
  277. \subsubsection{Numbering}
  278. We have to have a special [[setcounter]] so we can ignore the right stuff
  279. in the table of contents.
  280. <<control-sequence assignments>>=
  281. cstab["setcounter"] := Csetcounter
  282. ignore("addtocounter", "{{")
  283. \subsubsection{Other {\LaTeX} control sequences}
  284. <<control-sequence assignments>>=
  285. cstab["makeatletter"] := Cmakeatletter
  286. cstab["makeatother"] := Cmakeatother
  287. Here are all the old-style font changes.
  288. <<control-sequence assignments>>=
  289. fontchange("tt", "tt")
  290. fontchange("bf", "b")
  291. fontchange("it", "i")
  292. fontchange("sl", "i")
  293. fontchange("em", "em")
  294. ignore("rm")        # html can't switch to default font!
  295. ignore("sf")
  296. @ And some new ones
  297. <<control-sequence assignments>>=
  298. ignore("rmfamily")
  299. <<control-sequence assignments>>=
  300. ignoreenv("document")
  301. <<control-sequence assignments>>=
  302. substitution("LaTeX", "LaTeX")
  303. <<control-sequence assignments>>=
  304. ignore("numberline", "{")
  305. ignore("protect")
  306. ignore("twocolumn", "C")
  307. ignore("typeout", "[{")
  308. ignore("closedbib")
  309. <<control-sequence assignments>>=
  310. every ignore("leftmargini" | "leftmarginii" | "labelsep" | "fboxsep", "=")
  311. every ignore("tabcolsep", "=")
  312. \subsection{Control sequences from various {\LaTeX} packages}
  313. <<control-sequence assignments>>=
  314. ignoreenv("multicols", "{C")
  315. cstab["citeN"] := Ccite
  316. ignore("afterpage", "{")
  317. A (perhaps vain) attempt to implement \verb+\kill+.
  318. <<control-sequence assignments>>=
  319. cstab["kill"] := Ckill
  320. \subsection{Plain {\TeX} control sequences}
  321. <<control-sequence assignments>>=
  322. argblock("noalign", "<br>", "<br>")    # not clear what else to do...
  323. argblock("centerline", "<br>", "<br>")
  324. substitution("cr", "<br>")
  325. substitution("hrule", "<hr>")
  326. substitution("vrule", "|")
  327. substitution("hrulefill", "------")
  328. ignore("hbox")
  329. ignore("rlap")
  330. ignore("llap")
  331. ignore("vbox")
  332. ignore("vtop")
  333. ignore("message", "{")
  334. ignore("relax")
  335. ignore("null")
  336. ignore("offinterlineskip")
  337. <<control-sequence assignments>>=
  338. cstab["par"] := implicit_paragraph
  339. cstab["smallskip"] := implicit_paragraph
  340. cstab["medskip"] := implicit_paragraph
  341. cstab["bigskip"] := implicit_paragraph
  342. cstab["vskip"] := implicit_paragraph
  343. csclosure["vskip"] := "="
  344. We can't give the grouping control sequences their real meaning, because
  345. that would blow our brace balance when ignoring definitions and the like.
  346. The proper solution would be to distinguish between grouping and braces,
  347. but that would require much more sophistication than we've got just now.
  348. <<control-sequence assignments>>=
  349. every ignore("begingroup" | "endgroup" | "bgroup" | "egroup")
  350. <<control-sequence assignments>>=
  351. cstab["newif"]   := Cnewif
  352. cstab["iffalse"] := Ciffalse
  353. cstab["iftrue"]  := Ciftrue
  354. cstab["else"]    := Celse
  355. cstab["fi"]      := Cfi
  356. cstab["ifx"] := cstab["if"] := cstab["ifnum"] := Ciffalse
  357. Lots of assignable things:
  358. <<control-sequence assignments>>=
  359. ignore("let", "A=")
  360. every ignore("hfuzz" | "parindent" | "parskip" | "baselineskip", "=")
  361. every ignore("hbadness" | "hsize" | "vsize" | "overfullrule" | "tabskip", "=")
  362. substitution("hskip", " ", "=")
  363. <<control-sequence assignments>>=
  364. ignore("unskip")
  365. ignore("hss")
  366. ignore("phantom", "{")
  367. every ignore("kern" | "lower" | "spacefactor", "=") # a cheat, but works
  368. every ignore("clubpenalty" | "widowpenalty", "=")
  369. @ Other stuff to be ignored:
  370. <<control-sequence assignments>>=
  371. every ignore("expandafter" | "indent" | "noindent" | "leavevmode" | "strut")
  372. ignore("def", 1)
  373. <<control-sequence assignments>>=
  374. substitution("TeX", "TeX")
  375. \subsection{Other control sequences}
  376. I get to include my favorite {\TeX} hacks.
  377. We define ignoring loosely; the count denotes the number of balanced-brace pairs.
  378. We also ignore everything before an ignored balanced-brace pair, which means
  379. it works for \verb+\def+.
  380. <<control-sequence assignments>>=
  381. ignore("noweboptions", 1)
  382. Now, here are a couple of righteous hacks!
  383. The idea is that most views will ignore this stuff, but the indexer might
  384. use it to get clever about dumping chunks and all in the right places.
  385. <<control-sequence assignments>>=
  386. substitution("nowebindex", "<nowebindex>")
  387. substitution("nowebchunks", "<nowebchunks>")
  388. ignore("nowebsize")
  389. <<control-sequence assignments>>=
  390. envblock("fields",  "blockquote", &null, "[")     # lame; could try to <tt> 1st col
  391. envblock("fields*", "blockquote", &null, "{")     # lame; could try to <tt> 1st col
  392. ignore("citeauthoryear", "{{{")
  393. ignore("authoryear", "{{")
  394. substitution("bibrule", "--------")
  395. let("bibskip", "par")
  396. every cstab["anoncite"|"authorcite"] := Ccite
  397. This will always have to be patched by hand, but it may be worth it.
  398. <<control-sequence assignments>>=
  399. argblock("psfig", "<a href=\"", "\">PostScript</a>")
  400. ignore("pssilent")
  401. ignore("psnoisy")
  402. \section{The conversion engine}
  403. \label{engine}
  404. The converter doesn't have the luxury of working on the whole text at
  405. once;  instead it has to accept and convert a piece at a time.
  406. If I really understood co-expressions, I would surely make them sit up
  407. and beg.
  408. Since I don't, I keep some state around, and I pass continuations and
  409. closures like there's no tomorrow.
  410. \subsection{Basic conversion}
  411. Here's the basic engine, which works by string scanning.
  412. The initial boilerplate sets up the second argument (if any) as
  413. [[&subject]].
  414. We have the odd specials [["\0"]] and [["\1"]], which are
  415. used to delimit quoted code in noweb.
  416. Woe betide the hapless user who has real nulls or 1s in his {\LaTeX} file.
  417. <<*>>=
  418. procedure convert(S, optstring)
  419.   static specials
  420.   initial { 
  421.     <<initialization>>
  422.     <<control-sequence assignments>>
  423.     <<assign to dynamic-add table>>
  424.     specials := '\\{}<>"%$&~\n\0\1' 
  425.   if \optstring then return optstring ? convert(S)
  426.   else {
  427.     <<scan, convert, and return result>>
  428. If I were a good dog, I would make a state diagram.
  429. Since I'm not, I'll just say that we either
  430. accumulate text using the function [[S.text]], which exists for that
  431. purpose, or else we do something special upon encountering a special character.
  432. The [[<<take actions appropriate to new text>>]] 
  433. chunk may do something special with the text in
  434. case we're not in the default state (for example, we may be scanning
  435. for the end of a comment).
  436. Encountering a non-threatening character throws the converter into
  437. horizontal mode.
  438. <<scan, convert, and return result>>=
  439. <<take actions appropriate to new text>>
  440. if S.mode == "V" & any(~'\\{}<>%\n\t ') then S.mode := "H"
  441. emit_text(S, tab(upto(specials) | 0))
  442. while not pos(0) do 
  443.   if S.mode == "Q" then { # quoting
  444.     emit_text(S, tab(upto('\1') | 0))
  445.     if ="\1" then {
  446.       emit_text(S, "\1")
  447.       S.mode := "H"
  448.     }
  449.   } else {
  450.     case move(1) of {
  451.       "\\" : {<<control sequence>>}
  452.       "{"  : {<<take open-group actions>>}
  453.       "}"  : {<<take close-group actions>>}
  454.       "%"  : {<<comment>>}
  455.       "~"  : emit_text(S, " ") # should be   but netscape doesn't support it
  456.       "\n" : {<<newline>>}
  457.       "$"  : {<<dollar sign>>}
  458.       "&"  : {<<ampersand>>}
  459.       "\0" : {S.mode := "Q"; emit_text(S, "\0")}
  460.       # remaining cases simply escape HTML specials
  461.       "<"  : emit_text(S, "<")
  462.       ">"  : emit_text(S, ">")
  463.       "\"" : emit_text(S, """)
  464.     }
  465.     emit_text(S, tab(upto(specials) | 0))
  466. return 1(. S.the_text, S.the_text := "")  # what's been converted
  467. The definition of a converter's state is distributed.
  468. We've already seen the use of [[mode]].
  469. <<*>>=
  470. record state(mode <<other fields of state>>)
  471.     # mode is H, V, or M
  472. To create a new state, the default mode is vertical
  473. <<*>>=
  474. procedure converter(mode)
  475.   /mode := "V"
  476.   return state(mode <<initial values for other fields of state>>)
  477. To avoid repeated memory allocation, we provide a routine to reset a
  478. converter to its initial state.
  479. <<*>>=
  480. procedure reset(S)
  481.   <<code to reset [[S]]>>
  482.   return S
  483. The basic action performed by the
  484. [[S.text]] function is to accumulate converted text in [[S.the_text]].
  485. [[S.text]] is usually [[accumulate_text]].
  486. <<*>>=
  487. procedure accumulate_text(S, text)
  488.   S.the_text ||:= text
  489.   return
  490. <<other fields of state>>=
  491. , text, the_text
  492. <<initial values for other fields of state>>=
  493. , accumulate_text, ""
  494. <<code to reset [[S]]>>=
  495. S.text := accumulate_text
  496. S.the_text := ""
  497. [[emit_text]] just uses the current value of [[S.text]], provided we aren't
  498. currently ignoring tokens.
  499. Its primary use is to appear in closures, when we don't know what
  500. [[S.text]] will be when the closure is executed.
  501. <<*>>=
  502. procedure emit_text(S, text)
  503.   return if \S.ignoring then "" else S.text(S, text)
  504. <<other fields of state>>=
  505. , ignoring
  506. <<initial values for other fields of state>>=
  507. , &null
  508. <<code to reset [[S]]>>=
  509. S.ignoring := &null
  510. \subsection{Action and continuation hooks}
  511. We provide hooks so that actions can be taken at various points.
  512. The major ones are:
  513. \begin{description}
  514. \item[\tt newtext]
  515. When the next string is passed in for conversion.
  516. \item[open brace]
  517. After the next open brace or begin environment.
  518. \item[close brace]
  519. Before the next close brace or end environment.
  520. \end{description}
  521. \subsubsection{{\tt newtext}}
  522. [[newtext]] is a list of closures to be executed (actions to take)
  523.  when the next input comes.
  524. <<other fields of state>>=
  525. , newtext
  526. <<initial values for other fields of state>>=
  527. <<code to reset [[S]]>>=
  528. S.newtext := []
  529. A closure is simply a procedure with arguments.
  530. <<*>>=
  531. record closure(proc, args)
  532. [[before_next_newtext]] and [[after_next_newtext]] 
  533. add to the list of actions to be taken (at the left and right, respectively).
  534. <<*>>=
  535. procedure before_next_newtext(S, proc, args)
  536.   push(S.newtext, closure(proc, args))
  537. procedure after_next_newtext(S, proc, args)
  538.   put(S.newtext, closure(proc, args))
  539. When taking the actions, be careful to avoid infinite loop, e.g., on empty lines.
  540. <<take actions appropriate to new text>>=
  541. l := S.newtext
  542. S.newtext := []
  543. while c := get(l) do
  544.   c.proc!c.args
  545. Some control sequences temporarily override all actions to be taken on
  546. a new input, using [[delay_newtext]].
  547.  [[undelay_newtext]] restores actions.
  548. <<*>>=
  549. procedure delay_newtext(S)
  550.   S.delayed_newtext := S.newtext
  551.   S.newtext := []
  552.   return
  553. procedure undelay_newtext(S)
  554.   S.newtext := \S.delayed_newtext |
  555.     {write(&errout, "This can't happen: null delayed_newtext"); &null[0]}
  556.   S.delayed_newtext := &null
  557. <<other fields of state>>=
  558. , delayed_newtext
  559. <<initial values for other fields of state>>=
  560. , &null
  561. <<code to reset [[S]]>>=
  562. S.delayed_newtext := &null
  563. \subsubsection{Opening and closing groups}
  564. There's only one list of actions to be taken at the next open,
  565. but there's a whole stack of lists of actions to be taken at closes.
  566. <<other fields of state>>=
  567. , open, closes
  568. <<initial values for other fields of state>>=
  569. , [], []
  570. <<code to reset [[S]]>>=
  571. every S.open | S.closes := []
  572. <<*>>=
  573. procedure after_next_open(S, proc, args)
  574.   return put(S.open, closure(proc, args))
  575. procedure before_next_close(S, proc, args)
  576.   return push(S.closes[1], closure(proc, args)) # lost at top level
  577. procedure after_next_close(S, proc, args)
  578.   return put(S.closes[1], closure(proc, args)) # lost at top level
  579. <<take open-group actions>>=
  580. push(S.closes, []) # fresh set of closing tasks
  581. while c := get(S.open) do
  582.   c.proc!c.args
  583. <<take close-group actions>>=
  584. while c := get(S.closes[1]) do
  585.   c.proc!c.args
  586. pop(S.closes)
  587. <<old>>=
  588. procedure Cbegingroup(S, cs, cl)
  589.  <<take open-group actions>>
  590. <<old>>=
  591. procedure Cendgroup(S, cs, cl)
  592.  <<take close-group actions>>
  593. <<old control-sequence assignments>>=
  594. cstab["begingroup"] := Cbegingroup
  595. cstab["endgroup"]   := Cendgroup
  596. cstab["bgroup"]     := Cbegingroup
  597. cstab["egroup"]     := Cendgroup
  598. \subsection{Handling control sequences and environments}
  599. OK, to eat a control sequence, first scan it, then execute it using [[do_cs]].
  600. [[S.csletters]] records the current set of ``letters'' for control
  601. sequences (so we can interpret \verb+\makeatletter+).
  602. <<control sequence>>=
  603. cs := if pos(0) then ""
  604.       else if any(S.csletters) then tab(many(S.csletters))
  605.       else move(1)
  606. if /S.ignoring | cs == ("else"|"fi") | cstab[cs] === (Ciffalse|Ciftrue) then
  607.   do_cs(S, cs)
  608.   &null # error("### Ignoring \\", cs)
  609. <<other fields of state>>=
  610. , csletters
  611. <<initial values for other fields of state>>=
  612. , &letters
  613. <<code to reset [[S]]>>=
  614. S.csletters := &letters
  615. To execute a control sequence, look up its procedure in [[cstab]],
  616. and pass in the name of the control sequence, plus the closure
  617. argument from [[csclosure]].
  618. \label{cs-tables}
  619. <<*>>=
  620. global cstab, csclosure
  621. procedure do_cs(S, cs)
  622.   tab(many(' \t')) # skip white space following CS
  623.   if pos(0) | any('\n') then before_next_newtext(S, skipblanks, [S])
  624.   (cstab[cs])(S, cs, csclosure[cs])
  625.   return 
  626. <<initialization>>=
  627. cstab := table(unknown_cs)
  628. csclosure := table()
  629. The default action for an unknown control sequence is [[unknown_cs]].
  630. If the global [[show_unknowns]] is set we dump the control sequence into the 
  631. output in bold.  We save the unknown sequences for later warning messages.
  632. <<*>>=
  633. global show_unknowns
  634. procedure unknown_cs(S, cs, cl)
  635. #  if S.text === ignore_text then return # a bit of a hack  -- should no longer be needed
  636.   if \show_unknowns then S.text(S, "<b>\\" || cs || "</b>")
  637.   if not member(unknown_set, cs) then {
  638.     write(\unknown_file, "Warning: unknown control sequence \\", cs)
  639.     insert(unknown_set, cs)
  640.   return
  641. <<initialization>>=
  642. unknown_set := set()
  643. <<*>>=
  644. global cstab, csclosure, unknown_set
  645. The control sequences \verb+\begin+ and \verb+\end+ are treated
  646. specially,
  647. so we can have a similar machinery for environments.
  648. <<*>>=
  649. global begintab, endtab, begincl, endcl
  650. procedure do_begin(S, cs, cl)
  651.   (="{", env := tab(upto('}')), ="}") | error("botched \\begin{...}")
  652.   <<take open-group actions>>
  653.   (begintab[env])(S, env, begincl[env])
  654.   return 
  655. procedure do_end(S, cs, cl)
  656.   (="{", env := tab(upto('}')), ="}") | error("botched \\end{...}")
  657.   # write(&errout, "calling ", image(endtab[env]), " for \\end{", env, "}")
  658.   (endtab[env])(S, env, endcl[env])
  659.   <<take close-group actions>>
  660.   return 
  661. <<control-sequence assignments>>=
  662. cstab["begin"] := do_begin
  663. cstab["end"]   := do_end
  664. <<initialization>>=
  665. every begintab | endtab := table(unknown_env)
  666. every begincl  | endcl  := table()
  667. <<*>>=
  668. procedure unknown_env(S, env, cl)
  669. ###  if S.text === ignore_text then return # a bit of a hack # no longer needed
  670.   if \show_unknowns then S.text(S, "<b>{" || env || "}</b>")
  671.   if not member(unknown_envs, env) then {
  672.     write(\unknown_file, "Warning: unknown environment {", env, "}")
  673.     insert(unknown_envs, env)
  674.   return
  675. <<initialization>>=
  676. unknown_envs := set()
  677. <<*>>=
  678. global unknown_envs
  679. \subsection{Issuing warnings about unknown control sequences and environments}
  680. <<*>>=
  681. procedure warn_unknown(s, type, mark, rmark)
  682.   if *s > 0 then {
  683.     pushout("Unknown " || type || ": ")
  684.     every pushout(((\mark | "")\1) || !sort(s) || ((\rmark | "")\1) || " ")
  685.     pushout("\n")
  686. <<*>>=
  687. procedure pushout(s)
  688.   static col
  689.   initial col := 0
  690.   if find("\n", s) then
  691.     s ? {
  692.       pushout(tab(upto('\n')))
  693.       while ="\n" do {col := 0; write(&errout)}
  694.       pushout(tab(0))
  695.     }  
  696.   else {
  697.     col +:= *s
  698.     if col >= 79 then {writes(&errout, "\n  "); col := *s + 2}
  699.     writes(&errout, s)
  700.   return
  701. \subsection{Procedures related to parsing {\TeX}}
  702. \subsubsection{Comment-skipping}
  703. This logic gobbles text into [[S.comment]]
  704. until a newline is encountered, at which point it calls 
  705. [[Ccomment]] to format the comment.
  706. All other new-text actions go on hold until the comment is over.
  707. <<comment>>=
  708. parse_dynamic_add()
  709. delay_newtext(S)
  710. eat_comment(S)
  711. <<*>>=
  712. procedure eat_comment(S)
  713.   S.comment ||:= tab(upto('\n') | 0)
  714.   if pos(0) then
  715.     before_next_newtext(S, eat_comment, [S])
  716.   else {
  717.     undelay_newtext(S)
  718.     Ccomment(S)
  719.     S.comment := ""
  720.   return
  721. end    
  722. <<other fields of state>>=
  723. , comment
  724. <<initial values for other fields of state>>=
  725. <<code to reset [[S]]>>=
  726. S.comment := ""
  727. Verbatim text is a little bit like comment text.
  728. For verbatim environment, we have a tag for the corresponding HTML, 
  729. plus a string that terminates the environment.
  730. <<*>>=
  731. record verbatim_cl(html, terminator)
  732. procedure Cverbatim(S, cs, cl)
  733.   S.text(S, tag(\cl.html))
  734.   delay_newtext(S)
  735.   do_verbatim(S, cl)
  736.   return
  737. If we find the terminator, we're finished.
  738. Otherwise, we swallow the whole input and make sure our action on next
  739. input is to continue scanning.
  740. <<*>>=
  741. procedure do_verbatim(S, cl)
  742.   if verbatimout(S, tab(find(cl.terminator))) then {
  743.     =cl.terminator
  744.     S.text(S, endtag(\cl.html))
  745.     undelay_newtext(S)
  746.   } else {
  747.      verbatimout(S, tab(0))
  748.      before_next_newtext(S, do_verbatim, [S, cl])
  749.   return
  750. When writing verbatim text, we still have to convert HTML specials.
  751. <<*>>=
  752. procedure verbatimout(S, s)
  753.   s ? {
  754.     while S.text(S, tab(upto('&<>"'))) do
  755.       case move(1) of {
  756.         "\"" : S.text(S, """)
  757.         "&"  : S.text(S, "&")
  758.         "<"  : S.text(S, "<")
  759.         ">"  : S.text(S, ">")
  760.       }
  761.     S.text(S, tab(0))
  762.   return
  763. The \verb+\verb+ control sequence's terminator is the first character
  764. following \verb+\verb+
  765. <<*>>=
  766. procedure Cverb(S, cs, cl)
  767.   Cverbatim(S, cs, verbatim_cl("tt", move(1)))
  768.   return
  769. \subsubsection{Arguments}
  770. It's occasionally necessary to collect the argument of a control
  771. sequence.
  772. [[csarg]] does the job.
  773. <<*>>=
  774. procedure csarg(S)
  775.   return  2(="{", tab(bal('}', '{', '}')), ="}") |
  776.            (optwhite(), 
  777.                if ="\\" then 
  778.                  "\\" || (tab(many(S.csletters)) | move(1))
  779.                else
  780.                  move(1))
  781. \subsubsection{Misc specials}
  782. Ampersand is weak --- I just use some string depending on the environment.
  783. Tables look sort of OK.
  784. Notice that ampersands close and open groups.
  785. <<ampersand>>=
  786. <<take close-group actions>>
  787. emit_text(S, S.ampersand)
  788. <<take open-group actions>>
  789. <<other fields of state>>=
  790. , ampersand
  791. <<initial values for other fields of state>>=
  792. , " --- "
  793. The dollar sign is for entering and exiting math mode:
  794. <<dollar sign>>=
  795. if /S.ignoring then
  796.   if ="$" then
  797.     if S.mode == "M" then { Cdisplaymath_end(S); S.mode := "V" }
  798.     else                  { Cdisplaymath(S);     S.mode := "M" } 
  799.   else
  800.     if S.mode == "M" then { Cmath_end(S); S.mode := "H" }
  801.     else                  { Cmath(S);     S.mode := "M" } 
  802. Newlines emit themselves, plus start skipping blanks until they get to
  803. some nonblank text.
  804. We have to identify a blank line so we can insert a paragraph marker.
  805. <<newline>>=
  806. emit_text(S, "\n")
  807. if /S.ignoring then Cnewline(S)
  808. <<*>>=
  809. procedure Cnewline(S)
  810.   tab(many(' \t'))
  811.   if match("\n") then implicit_paragraph(S)
  812.   if pos(0) then before_next_newtext(S, Cnewline, [S])
  813. Other procedures might want to skip white space, which includes
  814. newlines, but we don't want to miss a paragraph.
  815. <<*>>=
  816. procedure skipblanks(S)
  817.   tab(many(' \t'))
  818.   if ="\n" then Cnewline(S)
  819.   else if pos(0) then before_next_newtext(S, skipblanks, [S])
  820. Paragraphs count only in horizontal or math mode (and they better not
  821. happen in math mode!).
  822. <<*>>=
  823. procedure implicit_paragraph(S, cs, cl)
  824.   if S.mode ~== "V" then {
  825.     S.mode := "V"
  826.     Cparagraph(S)
  827.   cs_ignore(S, cs, \cl)
  828. Here's a real hack.  I use it to stop skipping blanks when the noweb
  829. filter sees text quoted by [[[[...]]]].
  830. That text is never converted, but we don't want to skip blanks that
  831. follow it.
  832. <<*>>=
  833. procedure stop_skipping(S)
  834.   while S.newtext[1].proc === (Cnewline|skipblanks) do pop(S.newtext)
  835. \subsubsection{Items}
  836. For items, we actually want to do something with the optional arguments,
  837. namely, convert them.
  838. We wrap them in braces so that any font changes and so on will be 
  839. appropriately limited in their effects.
  840. <<*>>=
  841. record item_cl(before, after, ifnone)
  842. procedure Citem(S, cs, cl)
  843.   if pos(0) then 
  844.     after_next_newtext(S, Citem, [S, cs, cl])
  845.   else if ="[" then {
  846.     delay_newtext(S)
  847.     with_upto_bracket(S, "", convert_bracketed, cl)
  848.   } else {
  849.     skipblanks(S)
  850.     S.text(S, cl[1].ifnone)
  851. <<*>>=
  852. procedure convert_bracketed(S, contents, cl)
  853.   S.text(S, cl[1].before || 
  854.             convert(converter("H"), "{" || contents || "}") || 
  855.             cl[1].after)
  856.   optwhite()
  857. <<*>>=
  858. procedure listenv(env, html)
  859.   begintab[env] := Clist
  860.   begincl[env] := html
  861.   endtab[env] := Clist_end
  862.   endcl[env] := html
  863. procedure Clist(S, cs, cl)
  864.   S.text(S, tag(cl))
  865.   push(csclosure["item"], 
  866.     if cs == "description" then item_cl("<dt>", "<dd>", "<dt><dd>")
  867.     else                        item_cl("<li>", "--", "<li>"))
  868. procedure Clist_end(S, cs, cl)
  869.   S.text(S, endtag(cl))
  870.   pop(csclosure["item"])
  871. \subsubsection{Labels and references}
  872. These could be done by [[argblock]], except I want to make it possible to have
  873. different text depending on whether the references point forward or backward.
  874. <<*>>=
  875. global labels_seen
  876. procedure Clabel(S, cs, cl)
  877.   initial /labels_seen := set()
  878.   insert(labels_seen, l := csarg(S)) | fail
  879.   S.text(S, "<a name=\"" || l || "\"><b>[*]</b></a>")
  880. procedure Cref(S, cs, cl)
  881.   initial /labels_seen := set()
  882.   l := csarg(S) | fail
  883.   S.text(S, "<a href=\"#" || l || "\">[" || 
  884.                  (if member(labels_seen, l) then "<-" else "->") || "]</a>")
  885. \subsubsection{Citations}
  886. The important thing about a citation key is that it makes a hot line
  887. to the appropriate item in the bibliography.
  888. [[Ccite]] and [[Cbibitem]] work together to make it happen.
  889. Optional arg might contain blanks, so it might be split, but
  890.  I assume the citation key isn't split between inputs.
  891. <<*>>=
  892. procedure Ccite(S, cs, cl, bracketed_text)
  893.   if ="[" then {
  894.     delay_newtext(S)
  895.     with_upto_bracket(S, "", do_cite, cl)
  896.   } else
  897.     do_cite(S, &null, cl)
  898. procedure do_cite(S, commentary, cl)
  899.   local key
  900.   if \commentary then
  901.     optwhite()
  902.   if pos(0) then before_next_newtext(S, do_cite, [S, commentary, cl])
  903.   else {
  904.     key := csarg(S)
  905.     \commentary := convert(converter("H"), "{" || \commentary || "}")
  906.     S.text(S, "<b>[cite <a href=\"#NWcite-" || key || "\">" || key || "</a>" ||
  907.               (("<i>, " || \commentary || "</i>") | "" ) ||
  908.               "]</b>")
  909. <<*>>=
  910. procedure Cbibitem(S, cs, cl)
  911.   local label, key
  912.   static counter
  913.   initial counter := 0
  914.   if ="[" then {
  915.     delay_newtext(S)
  916.     with_upto_bracket(S, "", finish_bibitem, [])
  917.   } else {
  918.     label := "<b>[" || (counter +:= 1) || "]</b>"
  919.     key := csarg(S) | fail
  920.     S.text(S, "<br><a name=\"NWcite-" || key || "\">" || label || "</a> ")
  921. procedure finish_bibitem(S, contents, args)
  922.   local key, label
  923.   optwhite()
  924.   key := csarg(S) | fail
  925.   label := convert(converter("H"), "{" || contents || "}")
  926.   S.text(S, "<br><a name=\"NWcite-" || key || "\">" || label || "</a> ")
  927. \subsubsection{Conditionals}
  928. The idea here is that an \verb+\if+$\cdots$ control sequence will conditionally 
  929. ignore text, and that \verb+\fi+ restores the previous state.
  930. To keep track of state, we have an ``if stack'' that records what
  931. [[S.text]] should be upon encountering \verb+\else+ and \verb+\fi+.
  932. <<other fields of state>>=
  933. , ifstack
  934. <<initial values for other fields of state>>=
  935. <<code to reset [[S]]>>=
  936. if *S.ifstack > 0 then S.ifstack := []  # keeps GC down
  937. What's on the ifstack is
  938. <<*>>=
  939. record ifrec(on_else, on_fi)
  940. @ It's possible that one day this code will need to be updated to delay
  941. new-text actions (and to do God knows what if
  942. new-text actions have already been delayed).
  943. Every \verb+\if+$\cdots$ is equivalent either to \verb+\iffalse+
  944. of \verb+\iftrue+, so we begin by defining those, as well as \verb+\else+
  945. and \verb+\fi+
  946. <<*>>=
  947. procedure Ciffalse(S, cs, cl)
  948. #error("### \\", cs, " -> false (S.ignoring === ", image(S.ignoring) ? {="procedure "; tab(0)}, ")")
  949.   push(S.ifstack, ifrec(S.ignoring, S.ignoring))
  950.   S.ignoring := 1
  951. procedure Ciftrue(S, cs, cl)
  952. #error("### \\", cs, " -> true (S.ignoring === ", image(S.ignoring) ? {="procedure "; tab(0)}, ")")
  953.   push(S.ifstack, ifrec(1, S.ignoring))
  954. procedure Celse(S, cs, cl)
  955.   S.ignoring := S.ifstack[1].on_else
  956. #error("### \\else -> S.ignoring === ", image(S.ignoring) ? {="procedure "; tab(0)})
  957. procedure Cfi(S, cs, cl)
  958.   S.ignoring := S.ifstack[1].on_fi
  959. #error("### \\fi -> S.ignoring === ", image(S.ignoring) ? {="procedure "; tab(0)})
  960.   pop(S.ifstack)
  961. Now, all that's left is to handle \verb+\newif+.
  962. This part is all boilerplate.
  963. <<*>>=
  964. procedure Cnewif(S, cs, cl)
  965.   local newif, newcs
  966.   tab(many(' \t\n'))
  967.   if pos(0) then
  968.     after_next_newtext(S, Cnewif, [S, cs, cl])
  969.   else {
  970.     newif := csarg(S)
  971.     newif ?
  972.       if ="\\if" & newcs := tab(many(S.csletters)) & pos(0) then {
  973.         <<make [[newcs]] a new \verb+\if+-like thing>>
  974.       } else
  975.         error("\\newif argument botch: " || newif)
  976. And here we do the real work:
  977. <<make [[newcs]] a new \verb+\if+-like thing>>=
  978. cstab[newcs || "false"] := Csetif
  979. cstab[newcs || "true"]  := Csetif
  980. cstab["if" || newcs] := Ciffalse
  981. <<*>>=
  982. procedure Csetif(S, cs, cl)
  983.   local base, tag
  984.   if cs ? (base := tab(find("true"|"false")), tag := =("true"|"false"), pos(0)) then {
  985.     cstab["if" || base] := if tag == "true" then Ciftrue else Ciffalse
  986.   } else {
  987.     error("This can't happen --- setif botch (not urgent)")
  988. \subsection{Reading and converting auxiliary {\LaTeX} files}
  989. <<*>>=
  990. procedure auxfile(cs, ext, placeholder, header, ignore)
  991.   cstab[cs] := Cauxfile
  992.   csclosure[cs] := aux_cl(ext, placeholder, header, \ignore | "")
  993. [[Cauxfile]] succeeds if it finds a file, fails otherwise.
  994. <<*>>=
  995. record aux_cl(ext, placeholder, header, ignore)
  996. procedure Cauxfile(S, cs, cl)
  997.   local auxfile, T
  998.   if auxfile := open(basename(\curfile) || "." || cl.ext) then {
  999.     T := converter("V")
  1000.     Cmakeatletter(T)
  1001.     S.text(S, \cl.header)
  1002.     while line := read(auxfile) do 
  1003.       S.text(S, convert(T, line || "\n"))
  1004.     close(auxfile)
  1005.   } else {
  1006.     S.text(S, \cl.placeholder)
  1007.   cs_ignore(S, cs, cl.ignore)
  1008.   if \auxfile then return
  1009. <<*>>=
  1010. procedure basename(name)
  1011.   reverse(name) ? {
  1012.     tab(upto('.')) & ="."
  1013.     return reverse(tab(0))
  1014. \subsubsection{Table of contents}
  1015. We can build a table of contents by reading the .toc file.
  1016. Sadly, I haven't figured out how to get hot links yet.
  1017. <<control-sequence assignments>>=
  1018. cstab["contentsline"] := Ccontentsline
  1019. <<*>>=
  1020. procedure Ctableofcontents(S, cs, cl)
  1021.   S.mode := "V"
  1022.   Cauxfile(S, cs, cl)
  1023.   set_toclevel(S)
  1024. [[set_toclevel]] manages the starting and ending of lists.
  1025. With no level argument, it resets the toc to the initial level.
  1026. <<*>>=
  1027. procedure set_toclevel(S, l)
  1028.   static toclevel, initiallevel
  1029.   if /initiallevel := \l then
  1030.     S.text(S, "<ul compact>")
  1031.   if /l := \initiallevel then
  1032.     S.text(S, "</ul>")
  1033.   if /l then return  # never set a level
  1034.   /toclevel := l
  1035.   while toclevel < l do {
  1036.     S.text(S, "<ul compact>")
  1037.     toclevel +:= 1
  1038.   while toclevel > l do {
  1039.     S.text(S, "</ul>")
  1040.     toclevel -:= 1
  1041.   return
  1042. Assume one table of contents per converted document.
  1043. <<*>>=
  1044. procedure Ccontentsline(S, cs, cl) 
  1045.   local type, level
  1046.   static leveltab
  1047.   initial { <<assign numbers of sections in leveltab>> }
  1048.   l := \leveltab[csarg()] | fail
  1049.   if l > \countertab["tocdepth"] then
  1050.     cs_ignore(S, cs, "{{") # skip this one
  1051.   else {
  1052.     set_toclevel(S, l)
  1053.     S.text(S, "<li>")
  1054.     after_next_open(S, after_next_close, [S, cs_ignore, [S, cs, "{"]])
  1055. <<assign numbers of sections in leveltab>>=
  1056. l := ["part", "chapter", "section", "subsection", "subsubsection", 
  1057.       "paragraph", "subparagraph"]
  1058. leveltab := table()
  1059. every i := 1 to *l do
  1060.   leveltab[l[i]] := i - 2  # making section level 1
  1061. \subsubsection{Counters}
  1062. <<*>>=
  1063. global countertab
  1064. procedure Csetcounter(S, cs, cl)
  1065.   local counter
  1066.   (counter := csarg(), countertab[counter] := integer(csarg())) | 
  1067.     cs_ignore(S, cs, "{{")
  1068. <<initialization>>=
  1069. countertab := table()
  1070. \subsubsection{Accents}
  1071. This info is taken from the HTML RFC, section entitled 
  1072. ``ISO Latin~1 character entities.''
  1073. <<*>>=
  1074. global accent_name, accent_valid
  1075. <<initialization>>=
  1076. accent_name  := table()
  1077. accent_valid := table('')
  1078. accent_name ["`"]  := "grave"
  1079. accent_valid["`"]  := 'AEIOUaeiou'
  1080. accent_name ["'"]  := "acute"
  1081. accent_valid["'"]  := 'AEIOUYaeiouy'
  1082. accent_name ["^"]  := "circ"
  1083. accent_valid["^"]  := 'AEIOUaeiou'
  1084. accent_name ["\""] := "uml"
  1085. accent_valid["\""] := 'AEIOUaeiouy'
  1086. accent_name ["~"]  := "tilde"
  1087. accent_valid["~"]  := 'ANOano'
  1088. accent_name ["="]  := "bar"
  1089. accent_name ["."]  := "dot"
  1090. accent_name ["u"]  := "u"
  1091. accent_name ["v"]  := "v"
  1092. accent_name ["H"]  := "H"
  1093. accent_name ["t"]  := "t"
  1094. accent_name ["c"]  := "cedil"
  1095. accent_valid["c"]  := 'Cc'
  1096. accent_name ["d"]  := "underdot"
  1097. accent_name ["b"]  := "underbar"
  1098. Initialization calls [[accent]] to indicate that a control
  1099. sequence represents an accent.
  1100. In fact, [[accent]] is called on all keys of [[accent_name]].
  1101. <<*>>=
  1102. procedure accent(cs)
  1103.   cstab[cs] := Caccent
  1104. procedure Caccent(S, cs, cl)
  1105.   static warned
  1106.   initial warned := table()
  1107.   arg := csarg(S) | return
  1108.   if *arg = 1 & any(accent_valid[cs], arg) then
  1109.     S.text(S, "&" || arg || accent_name[cs] || ";")
  1110.   else {
  1111.     <<warn about [[cs]] with [[arg]]>>
  1112.     S.text(S, arg)
  1113. <<warn about [[cs]] with [[arg]]>>=
  1114. /warned[cs] := set()
  1115. if not member(warned[cs], arg) then {
  1116.   write(&errout, "Warning: Can't handle \\", cs, " with arg `", arg, "'")
  1117.   insert(warned[cs], arg)
  1118. \subsection{Font changes}
  1119. A font change changes the font until the next close, when we need to emit
  1120. the appropriate end tag.
  1121. <<*>>=
  1122. procedure fontchange(tex, html)
  1123.   cstab[tex] := Cfontchange
  1124.   csclosure[tex] := html
  1125. <<*>>=
  1126. procedure Cfontchange(S, tex, html)
  1127.   S.text(S, tag(html))
  1128.   before_next_close(S, emit_text, [S, endtag(html)])
  1129. \section{Implementations of declaratives}
  1130. \label{imp-decl}
  1131. \subsection{Ignoring stuff}
  1132. There are several different kinds of things that can be ignored:
  1133. ordinary arguments,
  1134. balanced-brace arguments, optional arguments, assignments (which may
  1135. include dimensions), stars, and parenthesized coordinates.
  1136. We ignore a sequence of these things by supplying a template to
  1137. [[ignore]], in which each character stands for something to be ignored.
  1138. We've already seen examples of these things in Section~\ref{cs-decls}.
  1139. We can ignore arguments of control sequences or environments.
  1140. In either case, [[cs_ignore]] does the work.
  1141. <<*>>=
  1142. procedure ignore(cs, template)
  1143.   /template := ""
  1144.   cstab[cs] := cs_ignore
  1145.   csclosure[cs] := template
  1146. procedure ignoreenv(env, template)
  1147.   /template := ""
  1148.   begintab[env] := cs_ignore
  1149.   begincl[env] := template
  1150.   endtab[env] := do_nothing
  1151. Because ignoring may span many inputs, all [[cs_ignore]] does is set things
  1152. up to call [[do_ignore]].  
  1153. The major setup is replacing [[S.text]] with a function that does nothing.
  1154. Oh, and it converts an integer template
  1155. into that many arguments, for historical reasons.
  1156. <<*>>=
  1157. procedure cs_ignore(S, cs, template, proc, args)
  1158.   local saved_ignore
  1159.   saved_ignore := S.ignoring
  1160.   S.ignoring := 1
  1161.   if type(template) == "integer" then template := repl("{", template)
  1162.   return do_ignore(S, template, saved_ignore, proc, args)
  1163. Some things are easily ignored (partly because we assume they don't
  1164. span inputs).  For others, we have special procedures.
  1165. The brace-ignoring stuff uses the open and close hooks, because braces
  1166. can be nested deeply.
  1167. If non-null, [[proc]] is applied to [[args]] after everything is ignored.
  1168. <<*>>=
  1169. procedure do_ignore(S, template, saved_ignore, proc, args)
  1170.   if *template > 0 then
  1171.     if optwhite() & pos(0) then
  1172.       after_next_newtext(S, do_ignore, [S, template, saved_ignore, proc, args])
  1173.     else
  1174.       case template[1] of {
  1175.         "{" : { S.ignoring := 1
  1176.                 after_next_open(S, ignore_til_close, 
  1177.                           [S, template[2:0], saved_ignore, proc, args])
  1178.               }
  1179.         "A" : { csarg(S) # had better be in one input
  1180.                 do_ignore(S, template[2:0], saved_ignore, proc, args)
  1181.               }
  1182.         "[" : if optwhite() & ="[" then {
  1183.                 delay_newtext(S)
  1184.         with_upto_bracket(S, "", ignore_bracket_plus,
  1185.                                       [S, template[2:0], saved_ignore, proc, args])
  1186.               } else
  1187.                 do_ignore(S, template[2:0], saved_ignore, proc, args)
  1188.         "C" : # a total cheat, means ``copy optional arg''
  1189.               if optwhite() & ="[" then {
  1190.                 S.ignoring := &null
  1191.                 delay_newtext(S)
  1192.         with_upto_bracket(S, "", copy_bracket_plus,
  1193.                                       [S, template[2:0], saved_ignore, proc, args])
  1194.               } else
  1195.                 do_ignore(S, template[2:0], saved_ignore, proc, args)
  1196.         "=" : { delay_newtext(S)
  1197.                 eat_assignment(S, do_ignore, [S, template[2:0], saved_ignore, proc,args])
  1198.               }
  1199.         "*" : { (="*", optwhite())
  1200.                 do_ignore(S, template[2:0], saved_ignore, proc, args)
  1201.               }
  1202.         "(" : { (="(", tab(upto(')')), =")", optwhite())
  1203.                 do_ignore(S, template[2:0], saved_ignore, proc, args)
  1204.               }
  1205.       }
  1206.   else {
  1207.     S.ignoring := saved_ignore
  1208.     (\proc)!(\args)
  1209. procedure ignore_til_close(S, template, saved_ignore, proc, args)
  1210.   before_next_close(S, do_ignore, [S, template, saved_ignore, proc, args])
  1211. Finally, at the end of an ignored environment, do nothing.
  1212. <<*>>=
  1213. procedure do_nothing(S, cs, cl)
  1214.   return
  1215. \subsubsection{Parsing bracketed (optional) arguments}
  1216. We may have to deal with optional arguments that are split across lines.
  1217. We pass in a continuation for the bracket.
  1218. This is a lot like gobbling to a newline, which we had to do with a comment.
  1219. As in the other case, we do something stupid if the bracket is
  1220. protected (e.g. by a backslash or comment char).
  1221. <<*>>=
  1222. procedure with_upto_bracket(S, bracketed_text, proc, args)
  1223.   bracketed_text ||:= tab(upto(']') | 0)
  1224.   if pos(0) then
  1225.     before_next_newtext(S, with_upto_bracket, [S, bracketed_text, proc, args])
  1226.   else {
  1227.     ="]"
  1228.     undelay_newtext(S)
  1229.     (\proc)(S, bracketed_text, args)
  1230.   return
  1231. end    
  1232. To ignore brackets:
  1233. <<*>>=
  1234. procedure ignore_bracket_plus(S, contents, args)
  1235.   # contents are ignored
  1236.   do_ignore!args
  1237. @ and to copy them
  1238. <<*>>=
  1239. procedure copy_bracket_plus(S, contents, args)
  1240.   local text
  1241.   text := args[3] | fail  # saved_ignore arg to do_ignore
  1242.   text(S, convert(converter("H"), "{" || contents || "}"))
  1243.   do_ignore!args
  1244. \subsubsection{Ignoring assignments}
  1245. Assignments are tricky because they might involve numbers, control
  1246. sequences, dimensions, or even glue.
  1247. We approximate the syntax from page 275 in the \TeX book.
  1248. <<*>>=
  1249. procedure eat_assignment(S, proc, args)
  1250.   static decimal_chars
  1251.   initial decimal_chars := &digits ++ '.,+-'
  1252.   optwhite()
  1253.   ="="        # so what if we swallow multiple = signs
  1254.   optwhite()
  1255.   if pos(0) then {
  1256.     before_next_newtext(S, eat_assignment, [S, proc, args])
  1257.     return
  1258.   } else if glue() then { # finished
  1259.   } else if any(decimal_chars) then {
  1260.     tab(many(decimal_chars))
  1261.     optwhite()
  1262.     if ="\\" then
  1263.       tab(many(S.csletters)) | move(1)
  1264.     # else assume assignment of the form \hangafter=2
  1265.   } else if ="\\" then
  1266.     tab(many(S.csletters)) | move(1)
  1267.   undelay_newtext(S)
  1268.   (\proc)!args
  1269. <<*>>=
  1270. procedure dimen()
  1271.   static decimal_chars
  1272.   initial decimal_chars := &digits ++ '.,'
  1273.   suspend (optwhite(), 
  1274.            if any('+-') then (move(1), optwhite()) else "",
  1275.            tab(many(decimal_chars)), optwhite(), 
  1276.            (="true", optwhite()) | &null,
  1277.            =("em"|"ex"|"pt"|"pc"|"in"|"bp"|"cm"|"mm"|"dd"|"cc"|"sp"|"mu"))
  1278. <<*>>=
  1279. procedure glue() 
  1280.   suspend (dimen(), 
  1281.            (optwhite(), ="plus",  dimen()) | "", 
  1282.            (optwhite(), ="minus", dimen()) | "")
  1283. \subsection{Substitution}
  1284. \subsubsection{Simple substitution for a single control sequence}
  1285. Even simple substitution isn't so simple, because in addition to the
  1286. HTML that we substitute for the {\TeX}, we can also supply a template
  1287. of stuff to be ignored (like the optional argument to \verb+\\+).
  1288. <<*>>=
  1289. procedure substitution(tex, html, ignore_template)
  1290.   # ignore mode for now
  1291.   cstab[tex] := Cemit_ig
  1292.   csclosure[tex] := emit_ig_cl(html, \ignore_template | "")
  1293. The closure contains HTML to be written and a template to be ignored.
  1294. <<*>>=
  1295. record emit_ig_cl(html, template)
  1296. procedure Cemit_ig(S, cs, cl)
  1297.   S.text(S, cl.html)
  1298.   if *cl.template > 0 then 
  1299.     cs_ignore(S, cs, cl.template)
  1300. \subsubsection{Substitution for environments}
  1301. The [[envblock]] procedure has two forms:
  1302. \begin{itemize}
  1303. \item
  1304. {}[[envblock(]]{\it environment}, {\it tag}[[)]] simply uses
  1305. begin- and end-{\it tag} in place of the environment.
  1306. \item
  1307. {}[[envblock(]]{\it environment}, {\it left}, {\it right}, {\it
  1308. ignore}[[)]] 
  1309. puts the {\it left} text at the beginning of the environment, the {\it
  1310. right} text at the end, plus at the beginning of the environment it
  1311. ignores the arguments described by {\it ignore}.
  1312. \end{itemize}
  1313. It's easier to implement than to describe.
  1314. <<*>>=
  1315. procedure envblock(env, left, right, ignore_template)
  1316.   /ignore_template := ""
  1317.   begintab[env] := Cemit_ig
  1318.   begincl[env] := emit_ig_cl(if /right then tag(left) else left, ignore_template)
  1319.   endtab[env] := Cemit
  1320.   endcl[env] := if /right then endtag(left) else right
  1321. @ [[Cemit]] emits text with nothing to ignore.
  1322. <<*>>=
  1323. procedure Cemit(S, cs, cl)
  1324.   S.text(S, cl)
  1325. \subsubsection{Substitution around arguments of control sequences}
  1326. These substitutions place tags at the beginning and end of arguments
  1327. to control sequences, instead of surrounding the contents of an
  1328. environment.
  1329. For example, they specify how to convert [[\section{...}]] to
  1330. [[<h1>...</h1>]] and so forth.
  1331. The calling convention is as for [[envblock]].
  1332. <<*>>=
  1333. record blockpair(left, right, ignore)
  1334. procedure argblock(tex, html, right, ignore)
  1335.   # called as is envblock
  1336.   /ignore := ""
  1337.   cstab[tex] := Cblock
  1338.   csclosure[tex] := 
  1339.     if /right then blockpair (tag(html),  endtag(html), ignore)
  1340.     else blockpair (html, right, ignore)
  1341. @ There is a fine point; control sequences labelled with [[argblockv]]
  1342. should put the converter into vertical mode.
  1343. <<*>>=
  1344. procedure argblockv(tex, html, right, ignore)
  1345.   argblock(tex, html, right, ignore)
  1346.   cstab[tex] := CblockV
  1347. <<*>>=
  1348. procedure Cblock(S, cs, cl, done_ignoring)
  1349.   if /done_ignoring & *cl.ignore > 0 then {
  1350.     cs_ignore(S, cs, cl.ignore, Cblock, [S, cs, cl, 1])
  1351.   } else if pos(0) then {
  1352.     after_next_newtext(S, do_cs, [S, cs, cl])
  1353.   } else if match("{") then {
  1354.     S.text(S, cl.left)
  1355.     after_next_open(S, before_next_close, [S, emit_text, [S, cl.right]])
  1356.   } else {
  1357.     S.text(S, cl.left || csarg(S) || cl.right)
  1358.   return
  1359. <<*>>=
  1360. procedure CblockV(S, cs, cl)
  1361.   S.mode := "V"
  1362.   Cblock(S, cs, cl)
  1363.   return
  1364. \subsection{Table environments}
  1365. For tables, we not only have an HTML tag, we also supply some text
  1366. for the ampersand.
  1367. [[args]] is a template describing the arguments to the environment,
  1368. which are ignored.
  1369. <<*>>=
  1370. record table_closure(args, amp, html)
  1371. procedure table_env(env, args, amp, html)
  1372.   begintab[env] := Ctable
  1373.   begincl[env] := table_closure(args, amp, html)
  1374.   endtab[env] := Ctable_end
  1375.   endcl[env] := []
  1376. <<*>>=
  1377. procedure Ctable(S, env, cl)
  1378.   local amp
  1379.   amp := S.ampersand
  1380.   S.ampersand := cl.amp
  1381.   S.text(S, tag(\cl.html))
  1382.   push(endcl[env], amp)
  1383.   cs_ignore(S, env, cl.args)
  1384. procedure Ctable_end(S, env, cl)
  1385.   S.ampersand := pop(cl)
  1386.   S.text(S, endtag(\begincl[env].html))
  1387. \subsection{Control-sequence assignment}
  1388. This procedure is available to be used for dynamic assignment.
  1389. One day we might use it to parse \verb+\let+ as well.
  1390. <<*>>=
  1391. procedure let(lhs, rhs)
  1392.   cstab[lhs] := cstab[rhs]
  1393.   csclosure[lhs] := csclosure[rhs]
  1394. \section{HTML formatting}
  1395. \label{html-format}
  1396. First, generic procedures used to create beginning and ending tags.
  1397. <<*>>=
  1398. procedure tag(html)
  1399.    return "<" || html || ">"
  1400. procedure endtag(html)
  1401.   return "</" || html || ">"
  1402. Next, a gazillion formatting procedures.
  1403. <<*>>=
  1404. procedure Ccomment(S)
  1405.   if *S.comment > 0 then {
  1406.     S.text(S, "<!--")
  1407.     S.comment ? {
  1408.       while S.text(S, tab(find("--"))) do {
  1409.         move(2)
  1410.         S.text(S, "- - ")
  1411.       }
  1412.       S.text(S, tab(0))
  1413.     }
  1414.     S.text(S, "-->")
  1415.   S.comment := ""
  1416.   return
  1417. <<*>>=
  1418. procedure Cparagraph(S)
  1419.   S.text(S, "<p>")
  1420. <<*>>=
  1421. procedure Cmath(S)
  1422.   <<take open-group actions>>
  1423.   S.text(S, "<i>")
  1424. procedure Cmath_end(S)
  1425.   S.text(S, "</i>")
  1426.   <<take close-group actions>>
  1427. <<*>>=
  1428. procedure Cdisplaymath(S)
  1429.   <<take open-group actions>>
  1430.   S.text(S, "<blockquote><i>")
  1431. procedure Cdisplaymath_end(S)
  1432.   S.text(S, "</i></blockquote>")
  1433.   <<take close-group actions>>
  1434. <<*>>=
  1435. procedure Cmakeatletter(S)
  1436.   S.csletters ++:= '@'
  1437. procedure Cmakeatother(S)
  1438.   S.csletters --:= '@'
  1439. Approximate \verb+\kill+ by eliminating text.
  1440. <<*>>=
  1441. procedure Ckill(S, cs, cl)
  1442.   S.the_text := ""
  1443. \section{Support for adding control sequences dynamically}
  1444. The idea is to use formal comments of the form:
  1445. \begin{quote}
  1446. \verb+% l2h function arg arg ...+
  1447. \end{quote}
  1448. These comments have the same effect as the procedure calls in
  1449. the chunk [[<<control-sequence assignments>>]].
  1450. Our first step is to create a table with the names of the functions we
  1451. recognize.
  1452. Ordinarly this table would be distributed, but I created it after the
  1453. fact with a little quick Unix pipeline.
  1454. <<*>>=
  1455. global csfunctions
  1456. <<initialization>>=
  1457. csfunctions := table()
  1458. <<assign to dynamic-add table>>=
  1459. csfunctions["argblock"] := argblock
  1460. csfunctions["argblockv"] := argblockv
  1461. csfunctions["envblock"] := envblock
  1462. csfunctions["fontchange"] := fontchange
  1463. csfunctions["ignore"] := ignore
  1464. csfunctions["ignoreenv"] := ignoreenv
  1465. csfunctions["let"] := let
  1466. csfunctions["listenv"] := listenv
  1467. csfunctions["substitution"] := substitution
  1468. Now, the tough issue is how to parse arguments.  I'm going to try the
  1469. following initial strategy:  arguments are separated by spaces.
  1470. To put a space within an argument, use \verb+#+.  There is no way to
  1471. put a \verb+#+ within an argument.
  1472. <<*>>=
  1473. procedure parse_dynamic_add(S)
  1474.    if (optwhite(), =("l2h"|"sl2h"), skipwhite(), 
  1475.        p := tab(upto(' \t')), <<make [[p]] a good function or warn and [[fail]]>>, 
  1476.        skipwhite(), any(~'\n')) then {
  1477.      a := []
  1478.      while any(~'\n') do {
  1479.        put(a, map(tab(upto(' \t\n') | 0), "#", " "))
  1480.        skipwhite()
  1481.      }
  1482.      p!a
  1483.      return
  1484. <<make [[p]] a good function or warn and [[fail]]>>=
  1485. ((p := \csfunctions[p]) | 
  1486. { dynamic_warn(p); fail })
  1487. <<*>>=
  1488. procedure dynamic_warn(p)
  1489.   static badprocs
  1490.   initial badprocs := set()
  1491.   if not member(badprocs, p) then {
  1492.     write(&errout, "Warning: % l2h ", p, " not recognized -- ignored")
  1493.     insert(badprocs, p)
  1494. @     
  1495. \section{Miscellanous utilities}
  1496. [[optwhite]] skips and returns optional white space.
  1497. <<*>>=
  1498. procedure optwhite()
  1499.   suspend tab(many(' \t')) | ""
  1500. @ [[skipwhite]] insists that there must be some white space.
  1501. <<*>>=
  1502. procedure skipwhite()
  1503.   suspend tab(many(' \t'))
  1504. \section{Main program for a noweb filter}
  1505. First, this is how we use the converter as a noweb filter.
  1506. <<l2h.icn>>=
  1507. <<*>>
  1508. procedure main(args)
  1509.   local line
  1510.   every arg := !args do
  1511.     case arg of {
  1512.       "-show-unknowns" : show_unknowns := 1
  1513.       default : write(&errout, "l2h filter: unknown arg ", image(arg))
  1514.     }
  1515.   while line := read() do
  1516.     apply(filter, line)
  1517.   warn_unknown(\unknown_set, "control sequences", "\\")
  1518.   warn_unknown(\unknown_envs, "environments", "{", "}")
  1519. procedure apply(pass, line)
  1520.     line ? (="@" & pass(tab(upto(' ')|0),  if =" " then tab(0) else &null))
  1521. This is noweb filter machinery.  I really ought to coordinate quoted text 
  1522. with the converter (so it always shows up in the right place), 
  1523. but so far I'm too lazy.
  1524. <<l2h.icn>>=
  1525. global curfile, curline
  1526. procedure filter(name, arg) 
  1527.   static S, C, code
  1528.   initial { S := converter("V"); C := converter("H") }
  1529.   case name of {
  1530.     "begin"    : {<<out>>; if match("code ", arg) then code := 1}
  1531.     "end"      : {<<out>>; code := &null; S.mode := "V"}
  1532.     "quote"    : { outtext("\0" ? convert(S)) }
  1533.     "endquote" : { outtext("\1" ? convert(S)) }
  1534.     "file"     : {<<out>>; curfile := arg; curline := 1}
  1535.     "line"     : {<<out>>; curline := integer(arg)}
  1536.     "defn"     : { write("@", name, " ", "{" || arg || "}" ? convert(C)); reset(C) }
  1537.     "use"      : { write("@", name, " ", "{" || arg || "}" ? convert(C)); reset(C) }
  1538.     "text"     : {if \code then <<out>> else outtext(arg ? convert(S))}
  1539.     "nl"       : {if \code then <<out>> else outtext("\n" ? convert(S)); curline +:= 1}
  1540.     default    : {<<out>>}
  1541.   return
  1542. <<out>>=
  1543. write("@", name, (" " || \arg) | "")
  1544. <<l2h.icn>>=
  1545. procedure outtext(s)
  1546.   s ? 
  1547.     while not pos(0) do
  1548.       if ="\n" then write("@nl")
  1549.       else if ="\0" then write("@quote")
  1550.       else if ="\1" then write("@endquote")
  1551.       else write("@text ", tab(upto('\n\0\1') | 0))
  1552.   return
  1553. <<l2h.icn>>=
  1554. procedure error(args[])
  1555.   return write!([&errout, (\curfile || ", ") | "", "line ", curline, ": "] ||| args)
  1556. \section{Main program for a simple converter}
  1557. <<sl2h.icn>>=
  1558. <<*>>
  1559. global curfile
  1560. procedure main(args)
  1561.   S := converter("V")
  1562.   every arg := !args do
  1563.     if arg[1] == "-" then
  1564.       case arg of {
  1565.         "-show-unknowns" : show_unknowns := 1
  1566.         default : write(&errout, "Warning: unrecognized option ", arg)
  1567.       }
  1568.     else if f := open(curfile <- arg) then
  1569.       while line := read(f) do writes(convert(S, line || "\n"))
  1570.     else
  1571.       write(&errout, "Error: Can't open file ", arg)
  1572.   if /curfile then
  1573.     while line := read() do writes(convert(S, line || "\n"))
  1574.   warn_unknown(\unknown_set, "control sequences", "\\")
  1575.   warn_unknown(\unknown_envs, "environments", "{", "}")
  1576. \section{Chunks}
  1577. \nowebchunks
  1578. \begin{multicols}{2}[\section{Index}]
  1579. \nowebindex
  1580. \end{multicols}
  1581. \end{document}
  1582.